﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace GameViewer.Ogre
{
    /// The endianness of written files
    enum Endian
    {
        /// Use the platform native endian
        ENDIAN_NATIVE,
        /// Use big endian (0x1000 is serialised as 0x10 0x00)
        ENDIAN_BIG,
        /// Use little endian (0x1000 is serialised as 0x00 0x10)
        ENDIAN_LITTLE
    };

    public class OgreSerializer
    {
        protected const UInt16 STREAM_OVERHEAD_SIZE = 6;
        protected const UInt16 HEADER_STREAM_ID = 0x1000;
        protected const UInt16 OTHER_ENDIAN_HEADER_STREAM_ID = 0x0010;
        protected const UInt16 OGRE_STREAM_TEMP_SIZE = 128;

        protected uint mCurrentstreamLen;
        protected string mVersion;
        protected bool mFlipEndian; // default to native endian, derive from header

        //---------------------------------------------------------------------
        protected void DetermineEndianness(BinaryReader reader)
        {
            //if (stream->tell() != 0)
            //{
            //    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
            //        "Can only determine the endianness of the input stream if it "
            //        "is at the start", "Serializer::determineEndianness");
            //}

            UInt16 dest;
            // read header id manually (no conversion)
            dest = reader.ReadUInt16();
            // skip back
            Skip(reader, 0 - 2);
            //if (actually_read != sizeof(uint16))
            //{
            //    // end of file?
            //    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
            //                "Couldn't read 16 bit header value from input stream.",
            //                "Serializer::determineEndianness");
            //}
            if (dest == HEADER_STREAM_ID)
            {
                mFlipEndian = false;
            }
            else if (dest == OTHER_ENDIAN_HEADER_STREAM_ID)
            {
                mFlipEndian = true;
            }
            else
            {
                //OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
                //    "Header chunk didn't match either endian: Corrupted stream?",
                //    "Serializer::determineEndianness");
            }
        }
        //---------------------------------------------------------------------
        protected T ReadChunk<T>(BinaryReader reader)
        {
            ushort id;
            ReadShorts(reader, out id, 1);
            ReadInts(reader, out mCurrentstreamLen, 1);
            T chunkID = (T)Enum.Parse(typeof(T), id.ToString());
            //Console.WriteLine(string.Format("chunkID:{0}",chunkID));
            return chunkID;
        }
        //---------------------------------------------------------------------
        protected void SkipChunk(BinaryReader reader)
        {
            Skip(reader, -STREAM_OVERHEAD_SIZE);
        }
        //---------------------------------------------------------------------
        protected void Skip(BinaryReader reader, long count)
        {
            reader.BaseStream.Seek(count, SeekOrigin.Current);
        }
        //---------------------------------------------------------------------
        protected bool IsEof(BinaryReader reader)
        {
            return !(reader.BaseStream.Position < reader.BaseStream.Length);
        }
        //---------------------------------------------------------------------
        protected void ReadBools(BinaryReader reader, out bool pDest, int count)
        {
            pDest = reader.ReadBoolean();
        }
        //---------------------------------------------------------------------
        protected void ReadFloats(BinaryReader reader, out float pDest, int count)
        {
            pDest = reader.ReadSingle();
        }
        //---------------------------------------------------------------------
        protected void ReadFloats(BinaryReader reader, out List<float> pDest, int count)
        {
            pDest = new List<float>();
            for (int i = 0; i < count; i++)
            {
                pDest.Add(reader.ReadSingle());
            }
        }
        //---------------------------------------------------------------------
        protected void ReadFloats(BinaryReader reader, out double pDest, int count)
        {
            pDest = reader.ReadDouble();
        }
        //---------------------------------------------------------------------
        protected void ReadShorts(BinaryReader reader, out ushort pDest, int count)
        {
            pDest = reader.ReadUInt16();
        }
        //---------------------------------------------------------------------
        protected void ReadShorts(BinaryReader reader, out List<ushort> pDest, int count)
        {
            pDest = new List<ushort>();
            for (int i = 0; i < count; i++)
            {
                pDest.Add(reader.ReadUInt16());
            }
        }
        //---------------------------------------------------------------------
        protected void ReadInts(BinaryReader reader, out uint pDest, int count)
        {
            pDest = reader.ReadUInt32();
        }
        //---------------------------------------------------------------------
        protected void ReadInts(BinaryReader reader, out List<uint> pDest, int count)
        {
            pDest = new List<uint>();
            for (int i = 0; i < count; i++)
            {
                pDest.Add(reader.ReadUInt32());
            }
        }
        //---------------------------------------------------------------------
        protected String ReadString(BinaryReader reader, int numChars)
        {
            char[] chars = reader.ReadChars(numChars);
            return new String(chars);
        }
        //---------------------------------------------------------------------
        protected int ReadChars(BinaryReader reader, ref char[] chars, int count)
        {
            if (reader.BaseStream.Position + count > reader.BaseStream.Length)
            {
                count = (int)(reader.BaseStream.Length - reader.BaseStream.Position);
            }
            long start = reader.BaseStream.Position;
            try
            {
                chars = reader.ReadChars(count);
            }
            catch
            {
                reader.BaseStream.Position = start;
                byte[] bytes = reader.ReadBytes(count);
                List<char> cl = new List<char>();
                foreach (byte b in bytes)
                {
                    cl.Add((char)b);
                }
                chars = cl.ToArray();
            }
            
            return count; 
        }
        //---------------------------------------------------------------------
        protected String ReadString(BinaryReader reader)
        {
            char[] tmpBuf = new char[OGRE_STREAM_TEMP_SIZE];
            String retString = "";
            int readCount;
            // Keep looping while not hitting delimiter
            while ((readCount = ReadChars(reader, ref tmpBuf, OGRE_STREAM_TEMP_SIZE - 1)) != 0)
            {
                // Terminate string
                //tmpBuf[readCount] = '\0';
                int i = 0;
                bool end = false;
                foreach (char c in tmpBuf)
                {
                    if (c == '\n')
                    {
                        Skip(reader, 1 + i - readCount);
                        tmpBuf[i] = '\0';
                        end = true;
                        break;
                    }
                    i++;
                }
                retString += new string(tmpBuf, 0, i);
                if (end)
                {
                    break;
                }
            }

            return retString;

            //List<char> chars = new List<char>();
            //char c = reader.ReadChar();
            //while (c != '\0')
            //{
            //    chars.Add(c);
            //    c = reader.ReadChar();
            //}
            //Skip(reader, -1);            
            //return new string(chars.ToArray());
        }

        protected float ReadFloat(byte[] buffer, int offset)
        {
            MemoryStream ms = new MemoryStream();
            ms.WriteByte(buffer[offset + 0]);
            ms.WriteByte(buffer[offset + 1]);
            ms.WriteByte(buffer[offset + 2]);
            ms.WriteByte(buffer[offset + 3]);
            ms.Position = 0;
            BinaryReader br = new BinaryReader(ms);
            return br.ReadSingle(); 
            //return ByteArrayToSingle(buffer, offset);
        }

        protected unsafe float ByteArrayToSingle(byte[] buffer, int offset)
        {
            fixed (byte* p = buffer)
                return *((float*)p);
        }

        //    void Serializer::flipToLittleEndian(void* pData, size_t size, size_t count)
        //{
        //    if(mFlipEndian)
        //    {
        //        flipEndian(pData, size, count);
        //    }
        //}

        //void Serializer::flipFromLittleEndian(void* pData, size_t size, size_t count)
        //{
        //    if(mFlipEndian)
        //    {
        //        flipEndian(pData, size, count);
        //    }
        //}

        //void Serializer::flipEndian(void * pData, size_t size, size_t count)
        //{
        //    for(uint index = 0; index < count; index++)
        //    {
        //        flipEndian((void *)((long)pData + (index * size)), size);
        //    }
        //}

        //void Serializer::flipEndian(void * pData, size_t size)
        //{
        //    char swapByte;
        //    for(uint byteIndex = 0; byteIndex < size/2; byteIndex++)
        //    {
        //        swapByte = *(char *)((long)pData + byteIndex);
        //        *(char *)((long)pData + byteIndex) = *(char *)((long)pData + size - byteIndex - 1);
        //        *(char *)((long)pData + size - byteIndex - 1) = swapByte;
        //    }
        //}     
    }
}
